# -*- coding: utf-8 -*-
import json
import chardet
from configparser import RawConfigParser

__version__ = '4.3.1.0'


class BaseConfig4():

    def __init__(self, filename: str, coding: str = 'auto', no_section_create=True, allow_no_value=False,
                 auto_save=True, read_only=False, alternate_file: str = None, alternate_coding: str = None):
        """
        传入：
            filename          str       文件路径
            coding            str       编码
            no_section_create bool      如果没有section则创建
            allow_no_value    str       是否允许没有值
            auto_save         bool      自动保存
            read_only         bool      只读模式
            alternate_file    None/str  候补文件，若原文件没有配置，允许在后补文件读取对应配置
                                        优先度 原文件配置 -> 候补文件配置 -> fallback值
            alternate_coding  None/str  候补编码，若指定，则在读取/存储失败时允许以新编码读取/存储
        """
        self._filename = filename
        self._coding = coding
        self._no_section_create = no_section_create
        self._allow_no_value = allow_no_value
        self.__read_only = read_only
        if not self.__read_only:
            self._auto_save = auto_save
        self._alternate_file = alternate_file
        self._alternate_coding = alternate_coding
        self.reload()

    @property
    def read_only(self):
        return self.__read_only

    @read_only.setter
    def read_only(self, value):
        print('read_only 仅可在初始化配置')

    def reload(self):
        """
        重新读取文件
        """
        self.config, self._coding = self._load_config_file(self._filename, self._coding, self._allow_no_value)
        if self._alternate_file:
            self.__alternate_config, _ = self._load_config_file(self._alternate_file, self._coding, self._allow_no_value)
        else:
            self.__alternate_config = None

    def _load_config_file(self, filename: str, coding: str = 'auto', allow_no_value=False):
        """
        读取配置文件
        :filename         str    文件名
        :coding           str    编码
        :allow_no_value   bool   是否允许空值
        res:
            ConfigParser    配置类
            str             编码类型
        """
        config = RawConfigParser(allow_no_value)
        with open(filename, 'rb+') as f:
            data = f.read()
            f.close()
        if coding == 'auto':
            coding = chardet.detect(data)['encoding']
        try:
            config.read_string(data.decode(coding))
        except Exception as e:
            if self._alternate_coding is None:
                raise e
            else:
                config.read_string(data.decode(self._alternate_coding))
                coding = self._alternate_coding
        return config, coding

    def save(self, filename='', encoding: str = None):
        """
        保存配置
        传入：
            filename    str   文件名,没有则自动写入原文件
            encoding    str   指定编码格式
        """
        if self.read_only:
            raise Exception(f'{self._filename} 不允许修改')
        if not filename:
            filename = self._filename
        if not encoding in ('auto', None):
            self._coding = encoding
        try:
            with open(filename, 'w+', encoding=self._coding) as f:
                self.config.write(f)
                f.close()
        except Exception as e:
            if self._alternate_coding is None or not encoding in ('auto', None):
                raise e
            else:
                with open(filename, 'w+', encoding=self._alternate_coding) as f:
                    self.config.write(f)
                    f.close()
                self._coding = self._alternate_coding
        return True

    def _make_sure_section(self, section: str):
        """
        确保section存在
        传入：
            section    str     section
        """
        if not section in self.config.sections():
            self.config.add_section(section)

    def get(self, section: str, option: str, fallback=None):
        """
        获取值
        传入:
            section   str
            option    str
            fallback  ...    如果没有值则返回
        """
        if self.config.has_option(section, option):
            value = self.config.get(section, option, fallback=None)
        else:
            if self.__alternate_config is None:
                return fallback
            else:
                value = self.__alternate_config.get(section, option, fallback=None)
        if value is None:
            return fallback
        _msg = value.split('@', 1)
        if len(_msg) <= 1:
            (_type, _value) = ('str', value)
        else:
            (_type, _value) = _msg
        if _type in (str.__name__,):
            _return = str(_value)
        elif _type in (int.__name__,):
            _return = int(_value)
        elif _type in (float.__name__,):
            _return = float(_value)
        elif _type in (bool.__name__,):
            _return = self.config._convert_to_boolean(_value)
        elif _type in (list.__name__, dict.__name__,):
            _return = json.loads(_value)
        elif _type in (set.__name__,):
            _return = set(json.loads(_value))
        elif _type == 'None':
            _return = None
        else:
            _return = None
        if _return is None and fallback is not None:
            return fallback
        else:
            return _return

    def set(self, section: str, option: str, value: object):
        """
        设值
        传入:
            section    str
            option     str
            value      ...    值
        """
        if self._no_section_create:
            self._make_sure_section(section)
        if isinstance(value, (list, dict,)):
            value = type(value).__name__ + '@' + json.dumps(value)
        elif isinstance(value, set):
            value = type(value).__name__ + '@' + json.dumps(list(value))
        elif isinstance(value, (int, float, str,)):
            value = type(value).__name__ + '@' + str(value)
        elif value is None:
            value = 'None@'
        else:
            raise Exception('value is unknow type')
        self.config.set(section, option, value)
        if self._auto_save:
            self.save()
        return True


if __name__ == '__main__':
    C = BaseConfig4('tx2.ini', allow_no_value=True)
    x = C.get('system', 'x', fallback='???')
    x = C.config.get('system', 'x', fallback='???')
    print(x, type(x))
    # .save()
